package pl.edu.agh.intobl;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;

import org.uncommons.maths.random.MersenneTwisterRNG;
import org.uncommons.maths.random.Probability;
import org.uncommons.watchmaker.framework.CandidateFactory;
import org.uncommons.watchmaker.framework.EvolutionEngine;
import org.uncommons.watchmaker.framework.EvolutionObserver;
import org.uncommons.watchmaker.framework.EvolutionaryOperator;
import org.uncommons.watchmaker.framework.FitnessEvaluator;
import org.uncommons.watchmaker.framework.GenerationalEvolutionEngine;
import org.uncommons.watchmaker.framework.PopulationData;
import org.uncommons.watchmaker.framework.SelectionStrategy;
import org.uncommons.watchmaker.framework.operators.EvolutionPipeline;
import org.uncommons.watchmaker.framework.selection.RankSelection;
import org.uncommons.watchmaker.framework.selection.RouletteWheelSelection;
import org.uncommons.watchmaker.framework.selection.SigmaScaling;
import org.uncommons.watchmaker.framework.selection.StochasticUniversalSampling;
import org.uncommons.watchmaker.framework.selection.TournamentSelection;
import org.uncommons.watchmaker.framework.termination.GenerationCount;
import org.uncommons.watchmaker.framework.termination.Stagnation;
import org.uncommons.watchmaker.swing.evolutionmonitor.EvolutionMonitor;

import pl.edu.agh.intobl.evaluators.RouteSetEvaluator;
import pl.edu.agh.intobl.factories.RouteSetFactory;
import pl.edu.agh.intobl.operators.RouteSetCrossOver;
import pl.edu.agh.intobl.operators.RouteSetDisplacement;
import pl.edu.agh.intobl.operators.RouteSetInsertion;
import pl.edu.agh.intobl.operators.RouteSetInversion;
import pl.edu.agh.intobl.operators.RouteSetSwap;
import pl.edu.agh.intobl.utils.InputParser;

/**
 * Solver for a problem specified in InputParser
 */
public class ProblemSolver {

	//params
	private static final int POPULATION = 50;
	private static final int ELITISM_COUNT = 1;
	//operators
	private static final double CROSSOVER_PROBABILITY = 0.5;
	private static final double SWAP_PROBABILITY = 0.1;
	private static final double INVERSION_PROBABILITY = 0.1;
	private static final double INSERTION_PROBABILITY = 0.4;
	private static final double DISPLACEMENT_PROBABILITY = 0.4;
	//selection strategy
	private static final int TOURNAMENT_SELECTION = 1;
	private static final int RANK_SELECTION = 2;
	private static final int ROULETTE_WHEEL_SELECTION = 3;
	private static final int SIGMA_SCALING = 4;
	private static final int STOACHSTIC_UNIVERSAL_SAMPLING = 5;
	private static final int SELECTION_STRATEGY = RANK_SELECTION;
	//evolution
	private static final long COMPUTING_TIME_LIMIT = 120000;
	private static final int STAGNATION_LIMIT = 5000;
	private static final int GENERATION_LIMIT = 2000;
	
	
	
	public static Integer vehiclesNr;
	public static Integer vehiclesCapacity;
	public static Map<Integer, Integer> demands = new HashMap<Integer, Integer>();
	public static Double[][] distanceMatrix;
	public static CustomersList customers;
	private static EvolutionMonitor<RouteSet> monitor = new EvolutionMonitor<RouteSet>();

	public static void start(InputParser parser) {
		monitor.showInFrame("population", true);
		vehiclesCapacity = parser.getVehiclesCapacity();
		customers = parser.getCustomers();
		distanceMatrix = customers.getDistanceMatrix();
		for (Customer customer : customers.getCustomers()) {
			if (customer.getId() != 0) {
				demands.put(customer.getId(), customer.getDemand());
			}
		}

		EvolutionEngine<RouteSet> engine = new GenerationalEvolutionEngine<RouteSet>(
				getCandidateFactory(), getEvolutionaryOperator(),
				getFitnessEvaluator(), getSelectionStrategy(),
				getRandomNumberGenerator());

		engine.addEvolutionObserver(new EvolutionObserver<RouteSet>() {
			public void populationUpdate(PopulationData<? extends RouteSet> data) {
				System.out.printf("Generation %d: %s %f\n", data.getGenerationNumber(), data.getBestCandidate(), data.getBestCandidateFitness());
				monitor.populationUpdate(data);
			}
		});

		RouteSet result = engine.evolve(POPULATION, ELITISM_COUNT, new Stagnation(STAGNATION_LIMIT,
				getFitnessEvaluator().isNatural()), new GenerationCount(GENERATION_LIMIT));
		System.out.println(result.fitness());
	}

	private static CandidateFactory<RouteSet> getCandidateFactory() {
		return new RouteSetFactory();
	}

	private static EvolutionaryOperator<RouteSet> getEvolutionaryOperator() {
		List<EvolutionaryOperator<RouteSet>> operators = new LinkedList<EvolutionaryOperator<RouteSet>>();
		operators.add(new RouteSetCrossOver(2, new Probability(CROSSOVER_PROBABILITY)));
		operators.add(new RouteSetSwap(SWAP_PROBABILITY));
		operators.add(new RouteSetInversion(INVERSION_PROBABILITY));
		operators.add(new RouteSetInsertion(INSERTION_PROBABILITY));
		operators.add(new RouteSetDisplacement(DISPLACEMENT_PROBABILITY));

		return new EvolutionPipeline<RouteSet>(operators);
	}

	private static FitnessEvaluator<RouteSet> getFitnessEvaluator() {
		return new RouteSetEvaluator();
	}

	private static SelectionStrategy<Object> getSelectionStrategy() {
		Probability prop = new Probability(0.85);
		switch(SELECTION_STRATEGY) {
			case TOURNAMENT_SELECTION: return new TournamentSelection(prop);
			case RANK_SELECTION: return new RankSelection();
			case ROULETTE_WHEEL_SELECTION: return new RouletteWheelSelection();
			case SIGMA_SCALING: return new SigmaScaling();
			case STOACHSTIC_UNIVERSAL_SAMPLING: return new StochasticUniversalSampling();
			
			default: return new TournamentSelection(prop);
		}
	}

	private static Random getRandomNumberGenerator() {
		return new MersenneTwisterRNG();
	}
}
